﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using QueuesData;
using System.ServiceModel;
using QueuesLib;

namespace QueuesClients {
    public class ClientsManager {
        public static readonly String OK_MSG = "OK";
        public static readonly String NOT_INITIALIZED_MSG = "Not initialized";
        public static readonly String UNEXPECTED_ARGUMENT_MSG = "Unexpected argument";

        private Dictionary<string, int> failures = new Dictionary<string, int>();
        private QueuesDataAdapter queuesData = new QueuesDataAdapter();

        public int MaxClientFailures {
            get {
                return Convert.ToInt32(ConfigurationManager.AppSettings.Get("MaxClientFailures"));
            }
        }

        public IEnumerable<IQueuesClient> ActiveClients {
            get {
                using (var db = new QueuesData.QueuesDBEntities()) {
                    var clients = from p in db.Clients
                                  where p.Active
                                  select p;
                    foreach (var c in clients) {
                        yield return GetClient(c.Id);
                    }
                    yield break;
                }
            }
        }

        public IEnumerable<InfoPanel> InfoPanels {
            get {
                using (var db = new QueuesData.QueuesDBEntities()) {
                    var infoPanels = from p in db.Clients
                                     where p.Type == QueuesData.ClientTypes.InfoPanel && p.Active
                                     select p;
                    foreach (var pn in infoPanels) {
                        yield return new InfoPanel(pn.Id, pn.IpAddress, pn.Port, pn.ServiceAddress);
                    }
                    yield break;
                }
            }
        }

        public IEnumerable<Kiosk> Kiosks {
            get {
                using (var db = new QueuesData.QueuesDBEntities()) {
                    var kiosks = from c in db.Clients
                                 where c.Type == QueuesData.ClientTypes.Kiosk && c.Active
                                 select c;
                    foreach (var cl in kiosks) {
                        yield return new Kiosk(cl.Id, cl.IpAddress, cl.Port, cl.ServiceAddress); ;
                    }
                    yield break;
                }
            }
        }

        public IEnumerable<WorkStation> WorkStations {
            get {
                using (var db = new QueuesData.QueuesDBEntities()) {
                    var stations = from p in db.Clients
                                   where p.Type == QueuesData.ClientTypes.WorkStation && p.Active
                                   select p;
                    foreach (var pt in stations) {
                        yield return new WorkStation(pt.Id, pt.IpAddress, pt.Port, pt.ServiceAddress); ;
                    }
                    yield break;
                }
            }
        }

        public void AddClient(string clientId, string clientIpAddress) {
            using (var db = new QueuesData.QueuesDBEntities()) {
                var client = db.Clients.Find(clientId);

                if (client == null) {
                    throw new UnauthorizedAccessException("Unknown client");
                }

                client.Active = true;
                client.IpAddress = clientIpAddress;
                db.SaveChanges();
                failures[client.Id] = 0;
                //InitializeClient(client.Id);
                Task.Run(() => InitializeClientAsync(client.Id));
            }
        }

        public void RemoveClient(string clientId) {
            using (var db = new QueuesData.QueuesDBEntities()) {
                var cl = db.Clients.Find(clientId);
                if (cl != null) {
                    cl.Active = false;
                    cl.IpAddress = null;
                    db.SaveChanges();
                }
            }
            failures.Remove(clientId);
        }

        private IQueuesClient GetClient(string idClient) {
            QueuesData.Client cl;
            using (var db = new QueuesData.QueuesDBEntities()) {
                cl = db.Clients.Find(idClient);
                if (cl == null) {
                    throw new Exception("Unknown client");
                }
            }

            switch (cl.Type) {
                case QueuesData.ClientTypes.InfoPanel:
                    return new InfoPanel(cl.Id, cl.IpAddress, cl.Port, cl.ServiceAddress);
                case QueuesData.ClientTypes.Kiosk:
                    return new Kiosk(cl.Id, cl.IpAddress, cl.Port, cl.ServiceAddress);
                case QueuesData.ClientTypes.WorkStation:
                    return new WorkStation(cl.Id, cl.IpAddress, cl.Port, cl.ServiceAddress);
                default:
                    throw new Exception("Unknown client type");
            }
        }

        public async Task InitializeClientAsync(string idClient) {
            try {
                var resp = await GetClient(idClient).InitializeAsync(queuesData.Subjects.ToList(),
                    queuesData.Counters.ToList(), queuesData.QueuesCurrentState.ToList());

                if (resp == NOT_INITIALIZED_MSG) {
                    throw new Exception("Error during client initialization");
                }
            }
            catch (FaultException f) {
                Console.WriteLine("Received fault: " + f.Message);
            }
            catch (CommunicationException c) {
                Console.WriteLine("Received com exception: " + c.Message);
            }
            catch (TimeoutException t) {
                Console.WriteLine("Received timeout: " + t.Message);
            }
        }

        public void InitializeClient(string idClient) {
            try {
                var resp = GetClient(idClient).Initialize(queuesData.Subjects.ToList(),
                    queuesData.Counters.ToList(), queuesData.QueuesCurrentState.ToList());
                if (resp == NOT_INITIALIZED_MSG) {
                    throw new Exception("Error during client initialization");
                }
            }
            catch (FaultException f) {
                Console.WriteLine("Received fault: " + f.Message);
            }
            catch (CommunicationException c) {
                Console.WriteLine("Received com exception: " + c.Message);
            }
            catch (TimeoutException t) {
                Console.WriteLine("Received timeout: " + t.Message);
            }
        }

        public async Task ReinitializeAllClientsAsync() {
            var tasks = (from c in ActiveClients
                         select new Tuple<string, Task<string>>(c.Id, c.InitializeAsync(queuesData.Subjects.ToList(),
                             queuesData.Counters.ToList(), queuesData.QueuesCurrentState.ToList()))).ToList();

            foreach (var t in tasks) {
                try {
                    var resp = await t.Item2;
                    ProcessResult(t.Item1, true);
                }
                catch (FaultException f) {
                    ProcessResult(t.Item1, false);
                    Console.WriteLine("Received fault: " + f.Message);
                }
                catch (CommunicationException) {
                    ProcessResult(t.Item1, false);
                }
                catch (TimeoutException) {
                    ProcessResult(t.Item1, false);
                }
            }
        }

        public async Task UpdateAllClientsAsync() {

            var tasks = (from p in WorkStations
                         select new Tuple<string, Task<string>>(p.Id, p.UpdateAllAsync(queuesData.Counters.ToList(),
                             queuesData.QueuesCurrentState.ToList()))).ToList();

            foreach (var t in tasks) {
                try {
                    var resp = await t.Item2;
                    ProcessResult(t.Item1, true);
                    if (resp != OK_MSG) {
                        InitializeClientAsync(t.Item1);
                    } 
                }
                catch (FaultException f) {
                    ProcessResult(t.Item1, false);
                    Console.WriteLine("Received fault: " + f.Message);
                }
                catch (CommunicationException) {
                    ProcessResult(t.Item1, false);
                }
                catch (TimeoutException) {
                    ProcessResult(t.Item1, false);
                }
            }
        }

        public async Task NotifyTicketCallAsync(QueueInfo info) {
            var tasks = (from p in InfoPanels
                         select new Tuple<string, Task<string>>(p.Id, p.UpdateQueueAsync(info))).ToList();

            foreach (var t in tasks) {
                try {
                    var resp = await t.Item2;
                    ProcessResult(t.Item1, true);
                    if (resp != OK_MSG) {
                        InitializeClientAsync(t.Item1);
                    }

                }
                catch (FaultException f) {
                    ProcessResult(t.Item1, false);
                    Console.WriteLine("Received fault: " + f.Message);
                }
                catch (CommunicationException) {
                    ProcessResult(t.Item1, false);
                }
                catch (TimeoutException) {
                    ProcessResult(t.Item1, false);
                }
            }
        }

        private void ProcessResult(string clientId, bool success) {
            if (success) {
                failures[clientId] = 0;
            } else {
                if (!failures.ContainsKey(clientId) || failures[clientId] >= MaxClientFailures) {
                    RemoveClient(clientId);
                } else {
                    failures[clientId]++;
                }
            }
        }

    }
}
